import datetime
import backtrader as bt
import pandas as pd
'''构建策略： 200天内有两次金叉开始开仓，并且止盈和止损都有两档，达到第一档平掉仓位的一半，达到第二档全平'''
class SmaCross2(bt.Strategy):
    def __init__(self):
        # 过程参数
        self.index = 0  # 回测次数
        self.cache = 100  # 至少需要的k线数量
        # 固定参数
        self.sl_ls = [0.01, 0.04]  # 止损挡位
        self.tp_ls = [0.015, 0.05]  # 止盈挡位
        self.fast_ma_period = 7  # 金叉周期
        self.slow_ma_period = 30  # 死叉周期
        self.interval = 200
        # 策略过程参数
        self.sl_num = 0  # 止损次数
        self.tp_num = 0  # 止盈次数
        self.overcross_num = 0
        self.wait_num = 0
        # self.roll_over_slippage = 2.0  # 每次移仓产生两个点的滑点
        # 策略指标
        sma1 = bt.ind.SMA(period=self.fast_ma_period)
        sma2 = bt.ind.SMA(period=self.slow_ma_period)
        self.crossover = bt.ind.CrossOver(sma1, sma2)  # 上穿信号
    def next(self):
        price = self.position.price  # 持仓均价
        volume = self.position.size  # 持仓量
        value = self.broker.get_value()  # 净值
        qty = value/self.data.close[0]
        # 策略如何开仓
        if not self.position:
            if self.crossover[0]>0 and self.overcross_num == 0:
                self.overcross_num += 1  # 上传次数加一
            elif self.crossover[0]>0 and self.overcross_num == 1:  # 如果发生第二次上穿
                self.buy(size=0.9 * qty)
                self.overcross_num = 0
                print("期限内两次上穿，成交")
            # 开仓条件不成立的情况
            if self.wait_num >self.interval and self.overcross_num == 1:
                self.overcross_num = 0
            if self.overcross_num == 1:
                self.wait_num += 1
        # 策略如何平仓
        else:
            if self.sl_num + self.tp_num == 0:  # 如果从未触发止盈止损
                if self.data.close[0] < price * (1-self.sl_ls[self.sl_num]):  # 触发止损第一档
                    self.close(size=0.5 * volume)
                    self.sl_num += 1
                elif self.data.close[0] > price * (1+self.tp_ls[self.tp_num]):  # 触发止盈第一档
                    self.close(size=0.5 * volume)
                    self.tp_num += 1
            elif self.sl_num == 1 and self.tp_num == 0:  # 如果已经事先触发止损一次第一档
                if self.data.close[0] < price * (1-self.sl_ls[self.sl_num]):  # 触发止损第二档
                    self.close(size=volume)  # 全平
                    self.sl_num = 0
                    self.tp_num = 0
                elif self.data.close[0] >= price * (1+self.tp_ls[self.tp_num]):
                    self.close(size=volume)
                    self.tp_num = 0
                    self.sl_num = 0
            elif self.sl_num == 0 and self.tp_num == 1:  # 如果发生一次止盈
                if self.data.close[0] > price*(1+self.tp_ls[self.tp_num]):  # 触发止盈第二档
                    self.close(size=volume)
                    self.tp_num = 0
                    self.sl_num = 0
                elif self.data.close[0] <= price*(1-self.sl_ls[self.sl_num]):  # 触发止损
                    self.close(size=volume)
                    self.sl_num = 0
                    self.tp_num = 0
    '''移仓时滑点的解决方法'''
    # def roll_over_position(self):
    #     # 1. 获取当前头寸大小
    #     size = self.getposition(self.data_old).size
    #     # 2. 平掉旧合约头寸
    #     self.close(data=self.data_old)
    #     # 3. 计算开仓新合约的价格：新合约当前价格 + 滑点（如果是做多移仓）
    #     #    注意：如果是空头，则是新合约价格 - 滑点
    #     execution_price = self.data_new.close[0] + self.roll_over_slippage
    #     # 4. 在新合约上开仓
    #     self.order = self.buy(data=self.data_new, price=execution_price, exectype=bt.Order.Limit, size=size)
    #     # 使用限价单并指定价格，模拟以较差的价格成交


cerebro = bt.Cerebro()

'''投送datafeed：2020到2025上半年螺纹钢前复权处理主连合约日线行情数据（已从米筐获取加载到本地）'''
df = pd.read_csv('Pre_RB_data.csv')
df['date'] = pd.to_datetime(df['date'])
df.set_index('date', inplace=True)
brf_daily = bt.feeds.PandasData(dataname = df,
                                fromdate = datetime.datetime(2020,1,1),
                                todate = datetime.datetime(2025,6,30))

cerebro.broker.setcommission(
    commission=0.0001,
    margin=0.0,
    mult=10,
    name=None,
    commtype=bt.CommInfoBase.COMM_PERC,  # 设置手续费类型为按百分比收取
    stocklike=False,
    percabs=False  # 表示commission参数类型是比例而不是绝对值
)
cerebro.broker.set_slippage_fixed(1.0)  # 设置滑点：一个最小变动价位1元/吨
cerebro.broker.setcash(300000)
cerebro.broker.set_shortcash(False)
cerebro.adddata(brf_daily)
cerebro.addstrategy(SmaCross2)
cerebro.run()
cerebro.plot(style='candle')












